(function (_) {
    var ViewPort = function (stage, options) {
        this.pixelPerUnit = 50;

        this.gridStep = function () {
            return this.pixelPerUnit;
        };

        this.width = function () {
            return stage.getWidth();
        };

        this.height = function () {
            return stage.getHeight();
        };

        // tìm tâm của viewPort
        this.center = function () {
            return {
                x:this.width() / 2, y:this.height() / 2
            }
        };

        // tìm gốc tọa độ
        // @note: hiện tại gốc tọa độ ở ngay giữa viewPort
        // @returns: tọa độ pixel của gốc tọa độ
        this.getOrigin = function () {
            return this.center();
        };

        // convert tọa độ pixel sang tọa độ trục
        // @params: pixel - tọa độ pixel
        // @returns: tọa độ trục
        this.getGeoPoint = function (pixel) {
            var center = this.getOrigin(),
//                distance = _.distance(pixel, center);
                distance = py.Math.distance(pixel, center);
            return this.getGeoVector(distance);
        };

        this.getGeoVector = function (vector) {
            return {
                x:vector.x / -this.pixelPerUnit,
                y:vector.y / this.pixelPerUnit
            }
        };

        this.getPixelPoint = function (unit) {
//            return _.addVector(this.getOrigin(), this.getPixelVector(unit));
            return py.Math.addVector(this.getOrigin(), this.getPixelVector(unit));
        };

        this.getPixelVector = function (unit) {
            return {
                x:unit.x * this.pixelPerUnit,
                y:unit.y * -this.pixelPerUnit
            }
        };

        this.getPixelLength = function (unit) {
            return unit * this.pixelPerUnit;
        }

        this.getBound = function () {
            return [
                this.getGeoPoint({x:0, y:0}),
                this.getGeoPoint({x:this.width(), y:this.height()})
            ]
        }

    };


    // exports
    window.py.ViewPort = ViewPort;


})(Helper);

var DrawingPad = function (containerId, options) {
    var $ = this, K = Kinetic,
        container = document.getElementById(containerId),
        _pad, viewPort, _selected, figures = new ObservableArray([]),
        _ = window.Helper,
        _initSysLayers,
        _continuousDrawing = false,
        layers = {
//            "system":[
//                new K.Layer({id:'axesLayer', detectionType:"pixel"}),
//                new K.Layer({id:'gridLayer', detectionType:"pixel"})
//
//            ],array.push(layers.system.trapLayer = new layer({name: "trap"}));
            "shapes":new K.Layer({name:"shapes", listening: false}),
            "lines":new K.Layer({name:"lines", listening: false}),
            "points":new K.Layer({name:"points", listening: true})
        };


    // default options
    options = _.extend({
        color:{
            axes:'green',
            grid:'gray'
        }
    }, options);


    _initSysLayers = function () {
        var layer = K.Layer, array = [];
        layers.system = {};
        array.push(layers.system.axesLayer = new layer({name:"axes", listening:false}));
        array.push(layers.system.gridLayer = new layer({name:"grid", listening:false}));
        array.push(layers.system.previewLayer = new layer({name:"preview", listening:false, throttle: 80}));
        array.push(layers.shapes);
        array.push(layers.lines);
        array.push(layers.points);
        array.push(layers.system.trapLayer = new layer({name:"trap"}));
        array.push(layers.system.selectableLayer = new layer({name:"selectable"}));

        array.forEach(function (l) {
            _pad.add(l);
        });

        layers.system.trapLayer.add(new K.Rect({
            width:viewPort.width(),
            height:viewPort.height()
        }));



    }
    this.init = function () {
        _pad = new K.Stage({
            container:container,
            width:container.scrollWidth,
            height:container.scrollHeight
        });

        viewPort = new py.ViewPort(_pad, {});
        _initSysLayers();

    }
    this.init();

    var axes = new function () {
        var layer = layers.system['axesLayer'],
            padding = 15,
            x, y;

        var createArrow = function (x, y, angle) {
            var group = new K.Group,
                l1, l2,
                config1 = {

                }

        }

        this.firstTime = function () {
            var center = _pad.getCenter(),
                size = _pad.getSize(),
                w = size.width,
                h = size.height;

            var axesX = new K.Line({
                    points:[padding, center.y, w - padding, center.y],
                    stroke:options.color.axes,
                    strokeWidth:2
                }),
                axesY = new K.Line({
                    points:[center.x, padding, center.x, h - padding],
                    stroke:options.color.axes,
                    strokeWidth:2
                });

            layer.add(x = axesX);
            layer.add(y = axesY);
        };

        this.resize = function () {
            var center = _pad.getCenter(),
                size = _pad.getSize(),
                w = size.width,
                h = size.height;

            x.setPoints([padding, center.y, w - padding, center.y]);
            y.setPoints([center.x, padding, center.x, h - padding]);
        };

        this.firstTime();

        return this;
    };

    var grid = new function () {
        var layer = layers.system['gridLayer'],
            last = {
                gap:50,
                paddingTop:0,
                paddingLeft:0,
                vCount:1, hCount:0,
                w:0, h:0
            },
            vLines = [], hLines = [];

        this.firstTime = function () {
            var w = viewPort.width() + 1,
                h = viewPort.height() + 1,
                c = viewPort.center(),
                s = viewPort.gridStep(),
                paddingLeft = c.x % s,
                paddingTop = c.y % s,
                i = paddingLeft;

            last.vCount = last.hCount = 0;

            // draw vertical lines
            while (i < w) {
                vLines.push(new K.Line({
                    points:[i, -.5, i, h - .5],
                    stroke:options.color.grid,
                    strokeWidth:1
                }));

                i += s;
                last.vCount++;
            }

            i = paddingTop;
            while (i < h) {
                hLines.push(new K.Line({
                    points:[0 - .5, i, w - .5, i],
                    stroke:options.color.grid,
                    strokeWidth:1
                }));

                i += s;
                last.hCount++;
            }


            [].concat(vLines, hLines).forEach(function (line) {
                layer.add(line);
            });

            last.step = s;
            last.paddingLeft = paddingLeft;
            last.paddingTop = paddingTop;
            last.w = w;
            last.h = h;
        };

        this.resize = function () {
            var step = viewPort.gridStep(),
                w = viewPort.width() + 1,
                h = viewPort.height() + 1,
                c = viewPort.center(),
                paddingLeft = c.x % step,
                paddingTop = c.y % step,
                temp, line;

            if (step != last.step) {
                // force redraw all
            } else {
                if (w > last.w) {
                    // extend horizontal lines' length
                    hLines.forEach(function (line) {
                        line.attrs.points[1].x = w + step * 2;
                    });
                    // add more vertical lines
                    temp = vLines[vLines.length - 1].attrs.points[0].x + step;
                    while (temp < w + step) {
                        line = new K.Line({
                            points:[temp, -.5, temp, h + step - .5],
                            stroke:options.color.grid,
                            strokeWidth:1
                        });
                        temp += step;
                        vLines.push(line);
                        layer.add(line);
                    }

                    last.w = w;
                }

                if (h > last.h) {
                    // extend vertical lines' length
                    vLines.forEach(function (line) {
                        line.attrs.points[1].y = h + step * 2;
                    });
                    // add more horizontal lines
                    temp = hLines[hLines.length - 1].attrs.points[0].y + step;
                    while (temp < w + step) {
                        line = new K.Line({
                            points:[0 - .5, temp, w - .5 + step, temp],
                            stroke:options.color.grid,
                            strokeWidth:1
                        });
                        temp += step;
                        hLines.push(line);
                        layer.add(line);
                    }

                    last.h = h;
                }
            }

            // transform
            var vector = {
                x:last.paddingLeft - paddingLeft,
                y:last.paddingTop - paddingTop
            };

            if (vector.x < (step / 2)) vector.x += step;
            if (vector.y < (step / 2)) vector.y += step;

            layer.setPosition(-vector.x, -vector.y)
        }

        this.firstTime();

        return this;
    };

    this.resize = function (w, h) {
        w = w || container.clientWidth;
        h = h || container.clientHeight;
        _pad.setSize(w, h);
        //_initSysLayers();
        figures.forEach(function (f) {
            f.draw(viewPort)
        });

        axes.resize();
        grid.resize();

    }

    this.continuousDrawing = function (value) {
        if (typeof value !== 'undefined') {
            _.isBoolean(value) && (_continuousDrawing = value);
            return $;
        }

        return _continuousDrawing;
    };

    var capture = new function () {
        var capturing = false,
            layer = layers.system.trapLayer,
            trap = layer.getChildren()[0];

        trap.setListening(false);

        this.on = function() {
            trap.on.apply(trap,arguments);
            return this;
        };

        this.off =function() {
            trap.off.apply(trap,arguments);
            return this;
        };

        this.start = function () {
            trap.setListening(true);
            layer.moveToTop();
            return this;
        };

        this.pause = function () {
            trap.setListening(false);
            layer.setZIndex(3);
            return this;
        };

        this.stop = function () {
            this.pause();
            trap.eventListeners = {};
            return this;
        };
    };

    var Selector = function(condition) {
        var selectable = [],
            selectLayer = layers.system.selectableLayer,
            revert = [],
            id = Helper.guid(),
            events = [];

        figures.forEach(function(figure) {
            if(condition(figure)) {
                selectable.push(figure)
            }
        });

        this.each = function (callback) {
            selectable.forEach(callback);
        };

        this.on = function(event, handler) {
            events.push([event,id].join('.'));
            selectable.forEach(function(figure) {
                var visual = figure.visual();
                visual.on([event,id].join('.'), function(evt) {
                    handler.apply(figure, [evt, visual]);
                });
            });

            return this;
        };

        this.off = function(event) {
            selectable.forEach(function(figure) {
                figure.visual().off(event);
            });

            return this;
        };

        this.selectable = function () {
            var array = [];
            selectable.forEach(function(f) {
                array.push(f);
            });
            return array;
        };

        this.start = function() {
            selectable.forEach(function(figure) {
                var visual = figure.visual(),
                    layer = visual.getLayer();
                visual.moveTo(selectLayer);
                revert.push(function () {
                    visual.moveTo(layer);
                })
            });
            selectLayer.moveToTop();
            return this;
        };

        this.stop = function() {
            revert.forEach(function(func) {
                func();
            });
            this.off(events.join(' '));
            return this;
        };
    };

    this.getCapture = function () {
        return capture;
    };

    this.getSelector = function(condition) {
        if(Helper.isFunction(condition)){
            return new Selector(condition);
        }
        var prop = String(condition);
        return new Selector(function(figure) {
            return figure[prop] === true;
        });
    };



    this.previewLayer = function () {
        return layers.system.previewLayer;
    };


    var points = [];

    figures.on('pushed', function (event) {
        var figure = event.data[0],
            layer;

        if (!!figure.isPoint) {
            layer = layers.points;
        } else if (!!figure.isLinearObject) {
            layer = layers.lines;
        } else {
            layer = layers.shapes;
        }

        figure.layer(layer);
        figure.initVisual();
        figure.draw(viewPort);

    })

    this.figures = figures;
    this.stage = function () {
        return _pad
    }
    this.viewPort = function () {
        return viewPort
    }


    _pad.draw();

    var _action = {isDone:true};

    this.registerAction = function (name, className) {
        if(this[name]) {
            throw new Error([name, ' is already existed'].join(''));
        }
        this[name] = function () {
            return drawActionByType(new py.Actions[className]($))
        }
    };

    this.registerAction('moveFigure', 'MoveAction');

    var drawActionByType = function (action) {
        if (!_action.isDone) {
            _action.stop();
        }

        _action = action;
        action.on('finished', onFinishDrawing);
        action.on('started', onStartDrawing);
        action.start();
        return action;
    };

    var onFinishDrawing = function (event) {
        //py.log('finished', this, arguments);
        if (_continuousDrawing) {
            this.start();
        }
    };
    var onStartDrawing = function () {
        //py.log('started', this, arguments);
    };

[]
    return this;
}


var drawingPad = new DrawingPad("drawingPad", { });

drawingPad.figures.on('pushed', function (event) {
    viewModel.figures.push(event.data[0]);
})

var viewModel = {
    figures:ko.observableArray([])
};

ko.applyBindings(viewModel);

var f = drawingPad.figures;
//f.push(new py.Figures.PointXY({x:0, y:2}));
//f.push(new py.Figures.PointXY({x:2, y:0}));
//f.push(new py.Figures.PointXY({x:2, y:2}));
//f.push(new py.Figures.Line2Point({points:[f[0], f[1]]}));
//f.push(new py.Figures.Line2Point({points:[f[2], f[1]]}));

